﻿namespace ClassyBlog.Web
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Web.Mvc;

    using ExtensionMethods;
    using Infrastructure;

    [ModelBinder(typeof(ArchivedDatesBinder))]
    public class ArchivedDates
    {
        private const string DefaultYearParameterName = "year";
        private const string DefaultMonthParameterName = "month";
        private const string DefaultDayParameterName = "day";

        private const string DefaultYearFormatString = "yyyy";
        private const string DefaultMonthFormatString = "MMMM, yyyy";
        private const string DefaultDayFormatString = "MMMM d, yyyy";

        private static string yearParameterName = DefaultYearParameterName;
        private static string monthParameterName = DefaultMonthParameterName;
        private static string dayParameterName = DefaultDayParameterName;

        private static string yearFormatString = DefaultYearFormatString;
        private static string monthFormatString = DefaultMonthFormatString;
        private static string dayFormatString = DefaultDayFormatString;

        public ArchivedDates(int? year, int? month, int? day)
        {
            Validate(year, month, day);

            Year = year;
            Month = month;
            Day = day;
        }

        public static string YearParameterName
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(yearParameterName))
                {
                    yearParameterName = DefaultYearParameterName;
                }

                return yearParameterName;
            }

            [DebuggerStepThrough]
            set
            {
                yearParameterName = value;
            }
        }

        public static string MonthParameterName
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(monthParameterName))
                {
                    monthParameterName = DefaultMonthParameterName;
                }

                return monthParameterName;
            }

            [DebuggerStepThrough]
            set
            {
                monthParameterName = value;
            }
        }

        public static string DayParameterName
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(dayParameterName))
                {
                    dayParameterName = DefaultDayParameterName;
                }

                return dayParameterName;
            }

            [DebuggerStepThrough]
            set
            {
                dayParameterName = value;
            }
        }

        public static string YearFormatString
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(yearFormatString))
                {
                    yearFormatString = DefaultYearFormatString;
                }

                return yearFormatString;
            }

            [DebuggerStepThrough]
            set
            {
                yearFormatString = value;
            }
        }

        public static string MonthFormatString
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(monthFormatString))
                {
                    monthFormatString = DefaultMonthFormatString;
                }

                return monthFormatString;
            }

            [DebuggerStepThrough]
            set
            {
                monthFormatString = value;
            }
        }

        public static string DayFormatString
        {
            [DebuggerStepThrough]
            get
            {
                if (string.IsNullOrWhiteSpace(dayFormatString))
                {
                    dayFormatString = DefaultDayFormatString;
                }

                return dayFormatString;
            }

            [DebuggerStepThrough]
            set
            {
                dayFormatString = value;
            }
        }

        public int? Year { get; private set; }

        public int? Month { get; private set; }

        public int? Day { get; private set; }

        public bool IsEmpty
        {
            get
            {
                return !Year.HasValue && !Month.HasValue && !Day.HasValue;
            }
        }

        public DateTime StartAt
        {
            get
            {
                return IsEmpty ?
                       Clock.MinimumDate :
                       GenerateDate(Year.GetValueOrDefault(Clock.MinimumDate.Year), Month.GetValueOrDefault(Clock.MinimumDate.Month), Day.GetValueOrDefault(Clock.MinimumDate.Day), true);
            }
        }

        public DateTime EndAt
        {
            get
            {
                if (IsEmpty)
                {
                    return Clock.MaximumDate;
                }

                var year = Year.GetValueOrDefault(Clock.MaximumDate.Year);
                var month = Month.GetValueOrDefault(Clock.MaximumDate.Month);

                return GenerateDate(year, month, Day.GetValueOrDefault(DateTime.DaysInMonth(year, month)), false);
            }
        }

        public static ArchivedDates CreateFrom(
            IDictionary<string, object> attributes)
        {
            var year = Convert(attributes, YearParameterName);
            var month = Convert(attributes, MonthParameterName);
            var day = Convert(attributes, DayParameterName);

            return new ArchivedDates(year, month, day);
        }

        public override string ToString()
        {
            string format = null;

            if (Day.HasValue && Month.HasValue && Year.HasValue)
            {
                format = DayFormatString;
            }
            else if (Month.HasValue && Year.HasValue)
            {
                format = MonthFormatString;
            }
            else if (Year.HasValue)
            {
                format = YearFormatString;
            }

            return string.IsNullOrWhiteSpace(format) ?
                   string.Empty :
                   StartAt.ToString(format, Culture.Current);
        }

        private static void Validate(int? year, int? month, int? day)
        {
            if (year.HasValue && 
                ((year < Clock.MinimumDate.Year) ||
                (year > Clock.MaximumDate.Year)))
            {
                throw new ArgumentOutOfRangeException(
                    "year",
                    TextMessages.
                    ArchivedDates_Validate_Invalid_Year_Range.
                    Interpolate(
                    Clock.MinimumDate.Year,
                    Clock.MinimumDate.Year));
            }

            if (month.HasValue)
            {
                if ((month.GetValueOrDefault() < 1) ||
                    (month.GetValueOrDefault() > 12))
                {
                    throw new ArgumentOutOfRangeException(
                        "month",
                        TextMessages.
                        ArchivedDates_Validate_Invalid_Month_Range);
                }

                if (!year.HasValue)
                {
                    throw new ArgumentException(
                        TextMessages.
                        ArchivedDates_Validate_Missing_Year_For_Month,
                        "year");
                }
            }

            if (!day.HasValue)
            {
                return;
            }

            if (!month.HasValue)
            {
                throw new ArgumentException(
                    TextMessages.ArchivedDates_Validate_Missing_Month,
                    "month");
            }

            if ((day < 1) ||
                (day > DateTime.DaysInMonth(
                year.GetValueOrDefault(), month.GetValueOrDefault())))
            {
                throw new ArgumentOutOfRangeException(
                    "day",
                    TextMessages.
                    ArchivedDates_Validate_Invalid_Day_Range.Interpolate(
                    DateTime.DaysInMonth(
                    year.GetValueOrDefault(), month.GetValueOrDefault())));
            }
        }

        private static DateTime GenerateDate(int year, int month, int day, bool start)
        {
            return new DateTime(
                year,
                month,
                day,
                start ? 0 : 23,
                start ? 0 : 59,
                start ? 0 : 59,
                start ? 0 : 999);
        }

        private static bool HasValue(
            IDictionary<string, object> values, string parameterName)
        {
            return values.ContainsKey(parameterName) &&
                !string.IsNullOrWhiteSpace(values[parameterName].ToString());
        }

        private static int? Convert(
            IDictionary<string, object> values, string parameterName)
        {
            int value;

            return (HasValue(values, parameterName) &&
                int.TryParse(values[parameterName].ToString(), out value)) ?
                (int?)value :
                null;
        }
    }
}